﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml;

namespace RrslLite.Net.Attributes
{
    public class DataContractSerializerOperationBehavior : IOperationBehavior
    {
        private OperationDescription operationDescription;

        public DataContractSerializerOperationBehavior(OperationDescription operation, DataContractFormatAttribute dataContractFormatAttribute)
            : this(operation)
        {
            
        }

        public DataContractSerializerOperationBehavior(OperationDescription operationDescription)
        {
            this.operationDescription = operationDescription;
        }

        public void AddBindingParameters(OperationDescription operationDescription,
                                         BindingParameterCollection bindingParameters)
        {
        }

        public void ApplyClientBehavior(OperationDescription operationDescription, ClientOperation clientOperation)
        {
            clientOperation.Formatter = new MyClientFormatter(operationDescription, this);
            clientOperation.SerializeRequest = !IsUntypedMessage(operationDescription.Messages[0]);
            clientOperation.DeserializeReply = operationDescription.Messages.Count > 1 &&
                                               !IsUntypedMessage(operationDescription.Messages[1]);
        }

        public void ApplyDispatchBehavior(OperationDescription operationDescription, DispatchOperation dispatchOperation)
        {
        }

        public void Validate(OperationDescription operationDescription)
        {
        }

        public virtual XmlObjectSerializer CreateSerializer(Type type, string name, string ns, IList<Type> knownTypes)
        {
            return new DataContractSerializer(type, name, ns, knownTypes);
        }

        public virtual XmlObjectSerializer CreateSerializer(System.Type type, XmlDictionaryString name, XmlDictionaryString ns, IList<System.Type> knownTypes)
        {
            return null; // (XmlObjectSerializer)new DataContractSerializer(type, name, ns, (IEnumerable<System.Type>)knownTypes, this.MaxItemsInObjectGraph, this.IgnoreExtensionDataObject, false, this.DataContractSurrogate, this.DataContractResolver);
        }

        private bool IsUntypedMessage(MessageDescription md)
        {
            return (md.Body.ReturnValue != null && md.Body.Parts.Count == 0 &&
                    md.Body.ReturnValue.Type == typeof (Message)) ||
                   (md.Body.ReturnValue == null && md.Body.Parts.Count == 1 && md.Body.Parts[0].Type == typeof (Message));
        }

        private class MyClientFormatter : IClientMessageFormatter
        {
            private OperationDescription operationDescription;
            private DataContractSerializerOperationBehavior serializerOperationBehavior;
            private List<Type> knownTypes;

            public MyClientFormatter(OperationDescription operationDescription,
                                     DataContractSerializerOperationBehavior serializerOperationBehavior)
            {
                this.operationDescription = operationDescription;
                this.serializerOperationBehavior = serializerOperationBehavior;
                this.knownTypes = new List<Type>();
                foreach (Type type in operationDescription.KnownTypes)
                {
                    this.knownTypes.Add(type);
                }
            }

            public object DeserializeReply(Message message, object[] parameters)
            {
                MessageDescription incomingMessage = this.operationDescription.Messages[1];
                MessagePartDescription returnPart = incomingMessage.Body.ReturnValue;
                XmlDictionaryReader bodyReader = message.GetReaderAtBodyContents();
                if (incomingMessage.Body.WrapperName != null)
                {
                    bool isEmptyElement = bodyReader.IsEmptyElement;
                    bodyReader.ReadStartElement(incomingMessage.Body.WrapperName, incomingMessage.Body.WrapperNamespace);
                    if (isEmptyElement) return null;
                }

                XmlObjectSerializer returnValueSerializer = this.serializerOperationBehavior.CreateSerializer(
                    returnPart.Type, returnPart.Name, returnPart.Namespace, this.knownTypes);
                object result = returnValueSerializer.ReadObject(bodyReader, false);

                if (incomingMessage.Body.WrapperName != null)
                {
                    bodyReader.ReadEndElement();
                }

                return result;
            }

            public Message SerializeRequest(MessageVersion messageVersion, object[] parameters)
            {
                Message result = Message.CreateMessage(
                    messageVersion,
                    this.operationDescription.Messages[0].Action,
                    new MyOperationBodyWriter(this.operationDescription, this.serializerOperationBehavior,
                                              this.knownTypes, parameters));
                return result;
            }

            private class MyOperationBodyWriter : BodyWriter
            {
                private OperationDescription operationDescription;
                private DataContractSerializerOperationBehavior serializerOperationBehavior;
                private object[] operationParameters;
                private IList<Type> knownTypes;

                public MyOperationBodyWriter(OperationDescription operationDescription,
                                             DataContractSerializerOperationBehavior serializerOperationBehavior,
                                             IList<Type> knownTypes, object[] operationParameters)
                    : base(true)
                {
                    this.operationDescription = operationDescription;
                    this.serializerOperationBehavior = serializerOperationBehavior;
                    this.knownTypes = knownTypes;
                    this.operationParameters = operationParameters;
                }

                protected override void OnWriteBodyContents(XmlDictionaryWriter writer)
                {
                    MessageDescription outgoingMessage = this.operationDescription.Messages[0];
                    if (outgoingMessage.Body.WrapperName != null)
                    {
                        writer.WriteStartElement(outgoingMessage.Body.WrapperName, outgoingMessage.Body.WrapperNamespace);
                    }

                    foreach (var bodyPart in outgoingMessage.Body.Parts)
                    {
                        XmlObjectSerializer serializer = this.serializerOperationBehavior.CreateSerializer(
                            bodyPart.Type, bodyPart.Name, bodyPart.Namespace, this.knownTypes);
                        serializer.WriteObject(writer, this.operationParameters[bodyPart.Index]);
                    }

                    if (outgoingMessage.Body.WrapperName != null)
                    {
                        writer.WriteEndElement();
                    }
                }
            }
        }
    }
}
